/**
 *  Copyright 2007-2008 University Of Southern California
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */


package edu.isi.pegasus.planner.partitioner.graph;


import java.util.List;
import java.util.LinkedList;
import java.util.Map;
import java.util.HashMap;
import java.util.Iterator;

/**
 * Does a topological sort on the Partition.
 *
 * @author Karan Vahi
 * @version $Revision$
 */

public class TopologicalSortIterator implements Iterator
{

    /**
     * The partition that has to be sorted.
     */
    private Graph mGraph;


    /**
     * An array that contains the  number of incoming edges to a node.
     */
    private int[] mInDegree;

    /**
     * A Map that returns the index into mInDegree map for a particular node
     * in graph. Maps a ID of the node to an int value, which is the index to
     * to the array containing the in degree for each node.
     *
     * @see #mInDegree
     */
    private Map mIndexMap;

    /**
     * The internal list of nodes that contains the nodes to be traversed.
     */
    private List<GraphNode> mQueue ;

    /**
     * The number of nodes in the graph.
     */
    private  int mOrder;

    /**
     * The overloaded constructor.
     *
     * @param p   the graph that has to be sorted.
     */
    public TopologicalSortIterator( Graph graph ){
        mGraph = graph;
        initialize();

        mOrder = mGraph.size();
        mQueue = new LinkedList();
        //add all the root nodes to queue first
        for( Iterator it = this.mGraph.getRoots().iterator(); it.hasNext(); ){
            mQueue.add( (GraphNode)it.next() );
        }
    }

    /**
     * Initializes the inDegree for each node of the partition.
     *
     */
    public void initialize(){
        //build up a inDegree map for each node.
        int order = mGraph.size();
        mInDegree = new int[ order ];
        mIndexMap = new HashMap( order );

        int index = 0;
        //each of the root nodes have in degree of 0
        for ( Iterator it = mGraph.getRoots().iterator(); it.hasNext(); ){
            GraphNode root = (GraphNode)it.next();
            mIndexMap.put( root.getID(), new Integer( index ) );
            mInDegree[ index++ ] = 0;
        }

        //determine inDegree for other nodes
        //in degree for a node is the number of incoming edges/parents of a node
        for( Iterator<GraphNode> it = mGraph.nodeIterator(); it.hasNext(); ){
            GraphNode node = it.next();
            if( node.getParents().isEmpty() ){
                //node is a root. indegree already assigned
                continue;
            }

            mIndexMap.put( node.getID(), new Integer( index) );
            mInDegree[ index++ ] = node.getParents().size();
        }

        //sanity check
        if( index != order){
            throw new RuntimeException( "Index does not match order of partition " );
        }

    }

    /**
     * Returns whether there are more nodes to be traversed in the graph or not.
     * 
     * @return boolean
     */
    public boolean hasNext() {
        return !mQueue.isEmpty();
    }

    /**
     * Returns the next node to be traversed
     *
     * @return
     */
    public Object next() {
        GraphNode node = mQueue.remove( 0 );
        String nodeID = node.getID();

        //traverse all the children of the node
        // GraphNode n = null;
        for( Iterator<GraphNode> it = node.getChildren().iterator(); it.hasNext() ;){
            GraphNode child = it.next();
            String childID = child.getID();

            //remove the edge from node to child by decrementing inDegree
            int index = index(childID);
             mInDegree[ index ] -= 1;

            if( mInDegree[ index ] == 0 ){
                //add the node to the queue
                mQueue.add( child );
            }
        }
        return node;
    }

    /**
     * Removes a node from the graph. Operation not supported as yet.
     */
    public void remove() {
        throw new UnsupportedOperationException("Not supported yet.");
    }



    


    /**
     * Returns the index of a particular node. The index is used as an index into
     * arrays.
     *
     * @param id  the id of the node.
     *
     * @return the index
     */
    private int index( String id ){
        return ((Integer)mIndexMap.get( id )).intValue();
    }

    
}
